package util

import (
	"errors"
	"github.com/golang-jwt/jwt/v5"
	"go-blog/config"
	"time"
)

// JWTClaims 自定义JWT载荷结构体
// 用于在Token中存储用户关键信息（避免敏感数据如密码）
type JWTClaims struct {
	UserID               uint   `json:"user_id"`  // 用户ID，用于标识用户身份
	Username             string `json:"username"` // 用户名，可选存储的用户信息
	jwt.RegisteredClaims        // 嵌入JWT标准声明，包含过期时间等信息
}

// GenerateToken 生成JWT令牌
// 参数：
//
//	userID uint：用户ID，用于标识用户
//	username string：用户名，附加存储的用户信息
//
// 返回值：
//
//	string：生成的JWT令牌字符串
//	error：生成过程中可能出现的错误
func GenerateToken(userID uint, username string) (string, error) {
	// 1. 构建自定义载荷
	claims := JWTClaims{
		UserID:   userID,
		Username: username,
		// 设置标准声明字段
		RegisteredClaims: jwt.RegisteredClaims{
			// 过期时间：从配置中获取过期时长，当前时间加上过期时长
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(config.Conf.JWT.Expires)),
			// 签发时间：当前时间
			IssuedAt: jwt.NewNumericDate(time.Now()),
			// 签发者：标识令牌的签发来源（通常为应用名称）
			Issuer: "blog-backend",
		},
	}

	// 2. 创建令牌：使用HS256算法和自定义载荷
	// HS256：HMAC-SHA256，一种对称加密算法，使用同一密钥进行签名和验证
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 3. 签名令牌：使用配置中的JWT密钥生成最终的令牌字符串
	// 密钥从全局配置中获取，确保与解析时使用的密钥一致
	return token.SignedString([]byte(config.Conf.JWT.Secret))
}

// ParseToken 解析并验证JWT令牌
// 参数：
//
//	tokenStr string：待解析的JWT令牌字符串
//
// 返回值：
//
//	*JWTClaims：解析后的自定义载荷
//	error：解析或验证失败时的错误信息
func ParseToken(tokenStr string) (*JWTClaims, error) {
	// 1. 解析令牌：使用自定义载荷类型和密钥验证函数
	token, err := jwt.ParseWithClaims(
		tokenStr,     // 待解析的令牌字符串
		&JWTClaims{}, // 用于接收解析结果的载荷指针
		func(token *jwt.Token) (interface{}, error) { // 密钥验证回调函数
			// 验证签名算法是否为预期的HS256
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, errors.New("invalid token signing method")
			}
			// 返回用于验证签名的密钥（与生成时使用的密钥一致）
			return []byte(config.Conf.JWT.Secret), nil
		},
	)

	// 2. 处理解析错误
	if err != nil {
		return nil, err // 可能的错误：令牌格式错误、签名验证失败等
	}

	// 3. 提取并验证载荷
	if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
		// 验证通过，返回解析后的载荷
		return claims, nil
	}

	// 4. 验证失败（如令牌过期、无效等）
	return nil, errors.New("invalid or expired token")
}
